<template>
  <div class="script-editor">
    <h1>剧本编辑器 - 自动分页 & 光标保持</h1>
    <div class="controls">
      <button @click="undo(activePage)">撤销 (Ctrl+Z)</button>
      <button @click="redo(activePage)">重做 (Ctrl+Shift+Z)</button>
      <button @click="insertSceneHeaderPrompt">插入场头</button>
      <button @click="exportPDF">导出 PDF</button>
      <button @click="exportWord">导出 Word</button>
    </div>
    <div class="editor-wrapper" ref="editorWrapper">
      <div
        class="page"
        v-for="(content, index) in pages"
        :key="index"
        :data-page="index + 1"
        contenteditable="true"
        :ref="el => pageRefs[index] = el"
        @input="() => onInput(index)"
        @compositionstart="onCompositionStart"
        @compositionend="() => onCompositionEnd(index)"
        @keydown="onKeyDown(index, $event)"
        @paste="onPaste($event, index)"
        spellcheck="false"
      >
        <!-- 页码由CSS伪元素显示 -->
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, nextTick, watch, onMounted } from 'vue'
import html2pdf from 'html2pdf.js'
import { Document, Packer, Paragraph } from 'docx'
import { saveAs } from 'file-saver'

const MAX_LINES = 44
const LINE_HEIGHT_PX = 24
const MAX_HEIGHT = MAX_LINES * LINE_HEIGHT_PX

const pages = ref([''])
const pageRefs = reactive([])
const undoStacks = ref([[]])
const redoStacks = ref([[]])
const activePage = ref(0)
let isComposing = false
let shouldMoveCaretToEnd = false
let lastCaretPos = 0
let isRestoringHistory = false
let pendingCaretAfterSceneHeader = false

// 光标位置保存/恢复函数
function saveCaretPosition(contextEl) {
  const selection = window.getSelection()
  if (!selection.rangeCount) return 0
  const range = selection.getRangeAt(0)
  const preRange = range.cloneRange()
  preRange.selectNodeContents(contextEl)
  preRange.setEnd(range.endContainer, range.endOffset)
  return preRange.toString().length
}

function restoreCaretPosition(contextEl, offset) {
  let currentNode = null
  let currentOffset = 0

  function traverse(node) {
    if (node.nodeType === 3) {
      const nextOffset = currentOffset + node.length
      if (offset <= nextOffset) {
        currentNode = node
        return true
      }
      currentOffset = nextOffset
    } else if (node.nodeType === 1) {
      for (let i = 0; i < node.childNodes.length; i++) {
        if (traverse(node.childNodes[i])) return true
      }
    }
    return false
  }

  traverse(contextEl)

  if (currentNode) {
    const range = document.createRange()
    const sel = window.getSelection()
    range.setStart(currentNode, offset - currentOffset)
    range.collapse(true)
    sel.removeAllRanges()
    sel.addRange(range)
  }
}

function moveCaretToEnd(el) {
  if (!el) return
  const range = document.createRange()
  range.selectNodeContents(el)
  range.collapse(false)
  const sel = window.getSelection()
  sel.removeAllRanges()
  sel.addRange(range)
}

// 监听pages，渲染内容到contenteditable div
watch(
  pages,
  (newPages) => {
    nextTick(() => {
      newPages.forEach((text, index) => {
        const pageEl = pageRefs[index]
        if (!pageEl) return

        // 清空所有节点
        while (pageEl.firstChild) pageEl.removeChild(pageEl.firstChild)

        // 逐行渲染
        const lines = text.split('\n')
        lines.forEach(line => {
          const match = line.match(/^\[SCENE_HEADER:(.*)\]$/)
          if (match) {
            // 插入场头span
            const span = document.createElement('span')
            span.textContent = match[1]
            span.contentEditable = 'false'
            span.setAttribute('data-scene-header', 'true')
            Object.assign(span.style, {
              userSelect: 'none',
              backgroundColor: '#fffbcc',
              padding: '2px 6px',
              borderRadius: '3px',
              marginRight: '4px',
              display: 'inline-block',
              width: '100%',
            })
            pageEl.appendChild(document.createElement('br'))
            pageEl.appendChild(span)
            pageEl.appendChild(document.createElement('br'))
          } else {
            if (line !== '' || pageEl.childNodes.length === 0) {
              pageEl.appendChild(document.createTextNode(line))
              pageEl.appendChild(document.createElement('br'))
            }
          }
        })
        // === 只对当前激活页设置光标到内容最后 ===
        const el = pageRefs[activePage.value]
        if (pendingCaretAfterSceneHeader) {
          // 找到场头span后的第一个br，将光标定位到其后
          if (el) {
            let found = false
            for (let i = 0; i < el.childNodes.length; i++) {
              const node = el.childNodes[i]
              if (!found && node.nodeType === 1 && node.getAttribute('data-scene-header') === 'true') {
                found = true
              } else if (found && node.nodeName === 'BR') {
                const sel = window.getSelection()
                const range = document.createRange()
                range.setStartAfter(node)
                range.collapse(true)
                sel.removeAllRanges()
                sel.addRange(range)
                el.focus()
                break
              }
            }
          }
          pendingCaretAfterSceneHeader = false
        } else if (shouldMoveCaretToEnd) {
          moveCaretToEnd(el)
          shouldMoveCaretToEnd = false
        } else {
          restoreCaretPosition(el, lastCaretPos)
        }
      })
    })
  },
  { deep: true }
)

function onInput(index) {
  if (isComposing) return
  activePage.value = index
  const el = pageRefs[index]
  if (!el) return

  // 检查场头是否还在
  const headerSpan = el.querySelector('span[data-scene-header="true"]')
  if (!headerSpan) {
    // 自动恢复场头
    const match = pages.value[index].match(/\[SCENE_HEADER:(.*?)\]/)
    if (match) {
      const span = document.createElement('span')
      span.textContent = match[1]
      span.contentEditable = 'false'
      span.setAttribute('data-scene-header', 'true')
      Object.assign(span.style, {
        userSelect: 'none',
        backgroundColor: '#fffbcc',
        padding: '2px 6px',
        borderRadius: '3px',
        marginRight: '4px',
        display: 'inline-block',
        width: '100%',
      })
      el.insertBefore(span, el.firstChild)
    }
  }

  // 保存光标位置
  lastCaretPos = saveCaretPosition(el)

  let text = ''
  el.childNodes.forEach((node) => {
    if (node.nodeType === 1 && node.getAttribute('data-scene-header') === 'true') {
      text += `\n[SCENE_HEADER:${node.textContent}]\n`
    } else if (node.nodeType === 3) {
      text += node.textContent
    } else if (node.nodeName === 'BR') {
      text += '\n'
    }
  })
  pages.value[index] = text
  pushHistory(index)

  nextTick(() => {
    splitOverflow(index)
    tryMergePages(index)
  })
}

function splitOverflow(index) {
  const el = pageRefs[index]
  if (!el) return

  const content = pages.value[index]
  if (!content) return

  const temp = document.createElement('div')
  Object.assign(temp.style, {
    position: 'absolute',
    visibility: 'hidden',
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word',
    width: el.clientWidth + 'px',
    lineHeight: LINE_HEIGHT_PX + 'px',
    font: getComputedStyle(el).font,
  })
  temp.innerText = content
  document.body.appendChild(temp)

  if (temp.clientHeight <= MAX_HEIGHT) {
    document.body.removeChild(temp)
    return
  }

  let guess = Math.floor(content.length * MAX_HEIGHT / temp.clientHeight)
  let head = content.slice(0, guess)
  let tail = content.slice(guess)

  while (temp.clientHeight > MAX_HEIGHT && guess > 0) {
    guess--
    head = content.slice(0, guess)
    tail = content.slice(guess)
    temp.innerText = head
  }

  pages.value[index] = head
  pages.value.splice(index + 1, 0, tail)
  undoStacks.value.splice(index + 1, 0, [tail])
  redoStacks.value.splice(index + 1, 0, [])

  nextTick(() => {
    splitOverflow(index + 1)
  })

  document.body.removeChild(temp)
}

function tryMergePages(index) {
  if (index < pages.value.length - 1) {
    if (!pages.value[index].trim()) {
      pages.value[index] = pages.value[index + 1]
      pages.value.splice(index + 1, 1)
      undoStacks.value.splice(index + 1, 1)
      redoStacks.value.splice(index + 1, 1)
      nextTick(() => {
        tryMergePages(index)
      })
    }
  }
}

function pushHistory(index) {
  if (isComposing || isRestoringHistory) return
  const text = pages.value[index]
  if (!undoStacks.value[index]) undoStacks.value[index] = []
  if (undoStacks.value[index].length === 0 || undoStacks.value[index][undoStacks.value[index].length - 1] !== text) {
    undoStacks.value[index].push(text)
    redoStacks.value[index] = []
  }
}

function undo(index) {
  const stack = undoStacks.value[index] || []
  const redoStack = redoStacks.value[index] || []
  if (stack.length < 2) return
  isRestoringHistory = true
  redoStack.push(stack.pop())
  const prev = stack[stack.length - 1]
  pages.value[index] = prev
  shouldMoveCaretToEnd = true
  nextTick(() => { isRestoringHistory = false })
}

function redo(index) {
  const redoStack = redoStacks.value[index] || []
  const stack = undoStacks.value[index] || []
  if (redoStack.length === 0) return
  isRestoringHistory = true
  const next = redoStack.pop()
  stack.push(next)
  pages.value[index] = next
  shouldMoveCaretToEnd = true
  nextTick(() => { isRestoringHistory = false })
}

function onCompositionStart() {
  isComposing = true
}
function onCompositionEnd(index) {
  isComposing = false
  onInput(index)
}

function onKeyDown(index, e) {
  const el = pageRefs[index]
  if (!el) return

  // 拦截Ctrl+Z/Ctrl+Shift+Z
  if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'z') {
    e.preventDefault()
    undo(index)
    return
  }
  if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'z') {
    e.preventDefault()
    redo(index)
    return
  }

  const selection = window.getSelection()
  if (!selection.rangeCount) return
  const range = selection.getRangeAt(0)

  if (!range.collapsed) {
    const frag = range.cloneContents()
    if (frag.querySelector('span[data-scene-header="true"]')) {
      e.preventDefault()
      return
    }
  }

  if (e.key === 'Backspace') {
    let offsetNode = range.startContainer
    let offset = range.startOffset
    // 如果光标在文本节点开头，且前一个节点是场头
    if (offset === 0) {
      let prev = getPreviousNode(offsetNode)
      if (prev && prev.nodeType === 1 && prev.getAttribute('data-scene-header') === 'true') {
        e.preventDefault()
        // 将光标定位到场头前
        const pageEl = pageRefs[index]
        if (pageEl) {
          const sel = window.getSelection()
          const newRange = document.createRange()
          newRange.setStartBefore(prev)
          newRange.collapse(true)
          sel.removeAllRanges()
          sel.addRange(newRange)
          pageEl.focus()
        }
        return
      }
    }
  }

  if (e.key === 'Delete') {
    let offsetNode, offset
    offsetNode = range.startContainer
    offset = range.startOffset
    if (offsetNode.nodeType === 3 && offset < offsetNode.textContent.length) {
      // allow
    } else {
      const next = getNextNode(offsetNode)
      if (next && next.nodeType === 1 && next.getAttribute('data-scene-header') === 'true') {
        e.preventDefault()
        return
      }
    }
  }
}

function getPreviousNode(node) {
  while (node && !node.previousSibling) {
    node = node.parentNode
  }
  if (!node) return null
  return node.previousSibling
}
function getNextNode(node) {
  while (node && !node.nextSibling) {
    node = node.parentNode
  }
  if (!node) return null
  return node.nextSibling
}

function insertSceneHeaderPrompt() {
  insertSceneHeader('我是场头')
}

function insertSceneHeader(content) {
  const index = activePage.value
  // 在当前光标处插入 \n[SCENE_HEADER:xxx]\n
  const el = pageRefs[index]
  if (!el) return
  const sel = window.getSelection()
  if (!sel.rangeCount) return
  const range = sel.getRangeAt(0)
  // 获取当前内容
  let text = ''
  el.childNodes.forEach((node) => {
    if (node.nodeType === 1 && node.getAttribute('data-scene-header') === 'true') {
      text += `\n[SCENE_HEADER:${node.textContent}]\n`
    } else if (node.nodeType === 3) {
      text += node.textContent
    } else if (node.nodeName === 'BR') {
      text += '\n'
    }
  })
  // 计算光标在text中的位置
  const caretPos = saveCaretPosition(el)
  // 在光标处插入场头占位符
  const newText = text.slice(0, caretPos) + `\n[SCENE_HEADER:${content}]\n` + text.slice(caretPos)
  pages.value[index] = newText
  shouldMoveCaretToEnd = true
  pushHistory(index)
}

function exportPDF() {
  const element = document.querySelector('.editor-wrapper')
  html2pdf()
    .set({
      margin: 0,
      filename: '剧本.pdf',
      image: { type: 'jpeg', quality: 0.98 },
      html2canvas: { scale: 2, useCORS: true },
      jsPDF: { unit: 'pt', format: 'a4', orientation: 'portrait' },
      pagebreak: { mode: ['css', 'legacy'] },
    })
    .from(element)
    .save()
}

function exportWord() {
  const doc = new Document({
    sections: pages.value.map((pageText, idx) => {
      let cleanText = pageText
      const pageEl = pageRefs[idx]
      if (pageEl) {
        const span = pageEl.querySelector('span[data-scene-header="true"]')
        if (span) {
          const headerText = span.textContent || ''
          if (cleanText.startsWith(headerText)) cleanText = cleanText.slice(headerText.length)
        }
      }

      return {
        properties: {},
        children: [
          ...cleanText.split('\n').map((line) => new Paragraph(line)),
          new Paragraph({
            text: `第 ${idx + 1} 页`,
            spacing: { before: 200 },
            thematicBreak: true,
            alignment: 'center',
            style: 'footer',
          }),
        ],
      }
    }),
  })

  Packer.toBlob(doc).then((blob) => saveAs(blob, '剧本.docx'))
}

function onPaste(e, index) {
  const selection = window.getSelection()
  if (!selection.rangeCount) return
  const range = selection.getRangeAt(0)
  const frag = range.cloneContents()
  if (frag.querySelector('span[data-scene-header="true"]')) {
    e.preventDefault()
    return
  }
}

onMounted(() => {
  pages.value.forEach((text, i) => {
    if (!undoStacks.value[i]) undoStacks.value[i] = []
    if (undoStacks.value[i].length === 0) {
      undoStacks.value[i].push(text)
    }
  })
})
</script>

<style scoped>
.script-editor {
  max-width: 920px;
  margin: 20px auto;
  font-family: 'Courier New', monospace;
}

.editor-wrapper {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.page {
  border: 1px solid #aaa;
  padding: 32px 24px;
  background: white;
  width: 794px;
  height: 1123px;
  box-sizing: border-box;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 24px;
  font-size: 16px;
  position: relative;
  outline: none;
  text-align: left;
}

.page::after {
  content: "第 " attr(data-page) " 页";
  position: absolute;
  bottom: 12px;
  left: 0;
  width: 100%;
  text-align: center;
  font-size: 14px;
  color: #777;
  pointer-events: none;
  user-select: none;
}

.controls {
  margin-top: 20px;
  display: flex;
  gap: 12px;
}
</style>
